#include "utilities.h"
#include "pass1.h"
#include "db.h"

#define LINE_MAX_LENGTH 1000 
#define numOfRegisters 8
#define NUM_START '#'
#define isRegisterType(type) ((type) == registerIndex ||(type)== registerImmediate)
#define printAtEndOfLine(ch) {\
fputc(' ',fp);\
fputc((ch),fp);\
fputc('\n',fp);\
}
#define endOfOperand() (isspace(*line) || *line == ',')

enum {immediate, direct, changeableIndex, registerIndex, registerImmediate};
enum {empty,regular,lable,dat,dot_str,ent,ext,comment};


FILE *ext_out;

int IC;
int DC;

/* parse .string line */
static void stringDirective(char *line, int lineNumber);
/* parse .data line */
static void dataDirective(char *line, int lineNumber);
/* prints data to the output file */
static void printDataArray(FILE *fp);
/* update temp Lables list and concat with the main one */
static void updateLablesList(int update_val);
/* creat ent file */
static void printEnt(FILE *ent_out);
/* print into ext file */
static void ptintExt(char *name);
/* print lable info into fp */
static void printLable(char *name, char *sAd, FILE *fp);
/* translate line and print it into fp
 * return the number of lines that where printed */
static void translateCommand(FILE *fp,char *line,int lineNumber);
/* check the operand type
 * op: the string that need to be checked
 * return the operand type */
static int opType(char *op);
/* check the line type
 * st: the line
 * return the line type */
static int lineType(char *st);
/* add the operand to tha array */
static int addOprand(char *array,int k,int opType ,char *operand);
/* print into fp the information about op */
static void printOperand(char *op, int opType, FILE *fp);
/* get string and translat it to number */
static int toNum(char *string);
/* print op (the lable) into fp */
static void printIndex(char *op, FILE *fp);
/* check if lable is extern (not yet finshed) */
static int isExtern(char *lable);
/* check if syntax of the command line is legal */
static int checkCommandSyntax(char *line, int numOfOperands);
/* check if syntax of the operand is legal */
static int checkOperandSyntax(char *line);
/* check if the string type line is legal */
static int checkStringLegalty(char *line, int lineNumber);
/* check if the is at line 2 coma neer one other */
static int checkIf2Coma(char *line);
/* check if have legal name */
int isLegalLable(char *lable);


void pass1(char *filename)
{
	int lineNumber = 1;
	int lineT;
	char line[LINE_MAX_LENGTH+2]; /* +1 for \0 and pluse more 1 for \n */
	char *p;
	char *p_derective;
	char *p_lable;
	FILE *asmIn;
	FILE *codeOut;
	FILE *ent_out;
	
	IC = 0;
	DC = 0;
	clear_list();
	asmIn = openFile(filename,".as","r");
	codeOut = openFile(filename,".ob1","w");
	ext_out = openFile(filename,".ext","w");
	ent_out = openFile(filename,".ent","w");
	
	while(fgets(line, LINE_MAX_LENGTH+2, asmIn) != NULL)
	{
		lineT = lineType(line);
		switch(lineT)
		{
			case regular:
			{
				translateCommand(codeOut, line, lineNumber);
				break;
			}
			case lable:
			{
				p_derective = line;
				p_lable = line;
				
				while(isspace(*p_lable))
					p_lable++;

				while(*p_derective != ':')
					p_derective++;
				*p_derective = '\0';
				p_derective++;
				
				if(isLegalLable(p_lable) == false)
				{
					do_error("unlegal lable name",lineNumber);
				}

				switch(lineType(p_derective))
				{
					case regular:
					{
						lable_list = list_add(lable_list, p_lable, IC);
						translateCommand(codeOut, p_derective, lineNumber);
						break;
					}
					case dat: /* data */
					{
						lable_list_tmp = list_add(lable_list_tmp, p_lable, DC);
						dataDirective(p_derective, lineNumber);
						break;
					}
					case dot_str: /* string */
					{
						lable_list_tmp = list_add(lable_list_tmp, p_lable, DC);
						stringDirective(p_derective, lineNumber);
						break;
					}
					default: /* error */
					{
						do_error("unlegal syntax",lineNumber);
					}
				}
				break;
			}
			case dat: /* data */
			{
				dataDirective(line, lineNumber);
				break;
			}
			case dot_str: /* string */
			{
				stringDirective(line, lineNumber);
				break;
			}
			case ent:
			case ext:
			{
				int localErr = 0;
				char *startOfLable;
				p = line;
				while(isspace(*p))
					p++;
				while(!isspace(*p))
					p++;
				while(isspace(*p))
					p++;
				startOfLable = p;
				while(!isspace(*p))
					p++;
				*p = '\0';
				p++;
				while(*p != '\0')
				{
					if(!isspace(*p))
					{
						do_error("unlegal syntax",lineNumber);
						localErr = ERROR;
						break;
					}
					p++;
				}
				
				if(isLegalLable(startOfLable) == false)
				{
					do_error("unlegal lable name",lineNumber);
					break;
				}
				
				if(localErr != ERROR)
				{
					if(lineT == ext)
						ext_list = list_add(ext_list,startOfLable,lineNumber);
					else
						ent_list = list_add(ent_list,startOfLable,lineNumber);
				}
				break;
			}
			case comment:
			case empty:
			{
				break;
			}
			default: /* error */
			{
				do_error("unlegal syntax",lineNumber);
			}
		}
		lineNumber++;
	}
	
	updateLablesList(IC);
	printDataArray(codeOut);

	printEnt(ent_out);
	
	
	listFree(ext_list);
	listFree(data_str_list);
	fclose(asmIn);
	fclose(codeOut);
	fclose(ext_out);
	fclose(ent_out);
}

/* check if have legal name */
int isLegalLable(char *lable)
{
	if(!isalpha(*lable))
		return false;
	lable++;
	while(*lable != '\0')
	{
		if(!isalnum(*lable))
			return false;
		lable++;
	}
	return true;
}

/* print into ent file */
static void printEnt(FILE *ent_out)
{
	char *sAd;
	node_ptr p = lable_list;
	node_ptr entp;
	while(p!=NULL) /* for every lable check if it is an entry */
	{
		entp = list_find(ent_list,p->name);
		if(entp != NULL)
		{
			sAd = toCharArray(p->value);
			printLable(entp->name ,sAd ,ent_out);
			free(sAd);
			ent_list = removeNode(ent_list,entp); /* if found the entry delet him for the list */
		}
		p = p->next;
	}
	while(ent_list != NULL) /* found all the entry that have no lable */
	{
		do_ent_error(ent_list->name,ent_list->value);
		ent_list = removeNode(ent_list,ent_list);
	}
}

/* print into ext file */
static void ptintExt(char *name)
{
	char *sAd = toCharArray(IC);
	printLable(name,sAd,ext_out);
	free(sAd);
}

/* print lable info into fp */
static void printLable(char *name, char *sAd, FILE *fp)
{
	int i = 0;
	int num;
	fprintf(fp,"%s ",name);
	while(i*2<outLineLength) /* skips zeros */
	{
		if(sAd[i*2] != '0' || sAd[i*2+1] != '0')
			break;
		i++;
	}
	
	if(i*2 == outLineLength) /* if all zeros print 0 */
	{
		fputc('0',fp);
		fputc('\n',fp);
		return;
	}
	
	while(i*2<outLineLength) /* print the address */
	{
		num = ((sAd[i*2] - '0')*2 + (sAd[i*2+1] - '0')) + '0';
		fputc(num,fp);
		i++;
	}
	fputc('\n',fp);
}

/* check if syntax of the operand is legal */
static int checkOperandSyntax(char *line)
{
	int numOfBracket = 0; /* check how much [ and ] appears */
	if(!isalpha(*line)) /* check if the first char is not char*/
	{
		if(*line != '#') /* if not char it must be # */
		{
			return false;
		}
		
		/* check if it is at number syntax */
		line++;
		if(*line == '+' || *line == '-')
			line++;
		while(!endOfOperand())
		{
			if(!isdigit(*line))
				return false;
			line++;
		}
		return true;
	}
	line++;
	while((!endOfOperand()) && *line != '\0')
	{
		if(!isalnum(*line)) /* can countain only digits and alph betic chars (and maibe [ and ]) */
		{
			if(numOfBracket == 2 || (*line != '[' && *line != ']'))
			{
				return false;
			}
			if(!((*line == '[' && numOfBracket == 0) || (*line == ']' && numOfBracket == 1)))
			{
				return false;
			}
			
			numOfBracket++;
		}
		line++;
	}
	if(numOfBracket == 0 || numOfBracket == 2) /* can countain only 1 [ and 1 ] */
		return true;
	return false;
}

/* check if syntax of the command line is legal */
static int checkCommandSyntax(char *line, int numOfOperands)
{
	while(isspace(*line))
		line++;
	switch(numOfOperands)
	{
		case 2: 
		{
			if(checkOperandSyntax(line) == false)
				return false;
			while(!endOfOperand() && *line != '\0')
				line++;
			while(isspace(*line))
					line++;
			if(*line != ',')
			{
				return false;
			}
			line++;
			while(isspace(*line))
				line++;
		}
		case 1:
		{
			if(checkOperandSyntax(line) == false)
				return false;
			while(!endOfOperand())
				line++;
			if(*line == ',')
				return false;
		}
		case 0:
		{
			while(*line != '\0')
			{
				if(!isspace(*line))
					return false;
				line++;
			}
			break;
		}
	}
	return true;
}

/* translate line and print it into fp
 * return the number of lines that where printed */
static void translateCommand(FILE *fp,char *line,int lineNumber)
{
	int i=0;
	int k=0;
	int op1type;
	int op2type;
	int length;
	int local_error = 0;
	char *op1;
	char *op2;
	char theCommand[5];
	char flOut[outLineLength];
	
	if(sscanf(line,"%s",theCommand)!= 1) /* check if empty line */
	{
		return;
	}
	
	while(isspace(*line))
		line++;
	
	length = strlen(theCommand);
	line = line + length + 1;
	
	/* finding the comand and put relvant info in the flOut array */
	i = findTheCommand(theCommand);
	if(numOfCommands == i) /* error, no such command */
	{
		do_error("no such command",lineNumber);
		return;
	}
	while(k<4)
	{
		flOut[k] = ((cmd[i]).code)[k];
		k++;
	}
	
	/* check how much operand there need to be */
	if((cmd[i]).op1Mode != mod_non)
	{
		if(checkCommandSyntax(line,2) == false) /* error */
		{
			do_error("unlegal syntax",lineNumber);
			return;
		}
		
		/* finding the operands */
		while(isspace(*line))
		{
			line++;
		}
		op1 = line;
		while(!isspace(*line) && *line != ',')
		{
			line++;
		}
		*line = '\0';
		line++;
		while(!isalpha(*line) && *line != NUM_START)
		{
			line++;
		}
		op2 = line;
		while(!isspace(*line))
		{
			line++;
		}
		*line = '\0';
		
		/* operand cant have command name */
		if(findTheCommand(op1) != numOfCommands) /* error */
		{
			do_error("the first operand have an command name",lineNumber);
			local_error = ERROR;
		}
		if(findTheCommand(op2) != numOfCommands) /* error */
		{
			do_error("the second operand have an command name",lineNumber);
			local_error = ERROR;
		}
	
		/* check the operands types legalty */
		op1type = opType(op1);
		op2type = opType(op2);
		if(op1type == ERROR)/* error */
		{
			do_error("there is no such type (first operand)",lineNumber);
			local_error = ERROR;
		}
		if(checkIfLegalOpType((cmd[i]).op1Mode,op1type) == false) /* error */
		{
			do_error("the type of the first operand is not legal",lineNumber);
			local_error = ERROR;
		}
		if(op2type == ERROR)/* error */
		{
			do_error("there is no such type (second operand)",lineNumber);
			local_error = ERROR;
		}
		if(checkIfLegalOpType((cmd[i]).op2Mode,op2type) == false) /* error */
		{
			do_error("the type of the second operand is not legal",lineNumber);
			local_error = ERROR;
		}
	
		/* add the operand into the flOut array */
		k = addOprand(flOut,k,op1type, op1);
		k = addOprand(flOut,k,op2type, op2);
	}
	else if((cmd[i]).op2Mode != mod_non)
	{
		if(checkCommandSyntax(line,1) == false) /* error */
		{
			do_error("unlegal syntax",lineNumber);
			return;
		}
		
		/* finding the operand */
		while(isspace(*line))
		{
			line++;
		}
		op2 = line;
		while(!isspace(*line))
		{
			line++;
		}
		*line = '\0';
		
		/* operand cant have command name */
		if(findTheCommand(op2) != numOfCommands) /* error */
		{
			do_error("the operand have an command name",lineNumber);
			local_error = ERROR;
		}
		
		/* check the operand type legalty */
		op2type = opType(op2);
		if(op2type == ERROR)/* error */
		{
			do_error("there is no such type (second operand)",lineNumber);
			local_error = ERROR;
		}
		if(checkIfLegalOpType((cmd[i]).op2Mode,op2type) == false) /* error */
		{
			do_error("the type of the operand is not legal",lineNumber);
			local_error = ERROR;
		}
		
		while(k<10)
		{
			flOut[k] = '0';
			k++;
		}
		k = addOprand(flOut,k,op2type, op2);
	}
	else
	{
		if(checkCommandSyntax(line,0) == false)
		{
			do_error("unlegal syntax",lineNumber);
			return;
		}
		while(k<outLineLength)
		{
			flOut[k] = '0';
			k++;
		}
	}
	
	
	if(local_error == ERROR)
	{
		return;
	}
		
	printLineAt4Base(flOut,fp); /* print flOut */
	printAtEndOfLine('a')
	IC++;
	
	/* print relevant info on lable and numbers */
	if((cmd[i]).op1Mode != mod_non)
		printOperand(op1,op1type,fp);
	if((cmd[i]).op2Mode != mod_non)
		printOperand(op2,op2type,fp);
}

/* print into fp the information about op */
static void printOperand(char *op, int opType, FILE *fp)
{
	switch(opType)
	{
		case direct:
		case changeableIndex:
		case registerIndex: {
			char *op2;
			char *help = op;
			
			/* finding the first lable abd print it */
			while(*help != '[' && *help != '\0')
				help++;
			*help = '\0';
			help++;
			printIndex(op,fp);
			IC++;
			
			if(opType == registerIndex || opType == direct) /* check if has more than 1 lable */
				break;
			
			/* finding the first lable abd print it */
			op2 = help;
			while(*help != ']')
				help++;
			*help = '\0';
			
			printIndex(op2,fp);
			IC++;
			
			break;
		}
		case immediate: {
			int num;
			char *sNum;
			
			op++;
			num = toNum(op); /* convert op into number */
			if(num<0) /* if negetiv use the compliting to, possible becouse int is more than 16 bits */
			{
				int i = 1;
				int mask = 1;
				while(i<16)
				{
					mask = mask*2 + 1;
					i++;
				}
				num = mask + num + 1;
			}
			/* print the number */
			sNum = toCharArray(num);
			printLineAt4Base(sNum,fp);
			printAtEndOfLine('a')
			IC++;
			
			free(sNum);
			
			break;
		}
		default: break;
	}
}

/* get string and translat it to number */
static int toNum(char *string)
{
	int num = 0;
	int sign;
	if(*string == '-')
		sign = -1;
	else
		sign = 1;
	if(*string == '-' || *string == '+')
			string++;
	
	while(*string != '\0')
	{
		num *= 10;
		num += (*string)-'0';
		string++;
	}
	num *= sign;
	return num;
}

/* print lable into fp */
static void printIndex(char *lable, FILE *fp)
{
	int i=0;
	
	if(isExtern(lable) == true) /* check if lable is external */
	{
		ptintExt(lable);
		while(i < (outLineLength/2))
		{
			fputc('0',fp);
			i++;
		}
		printAtEndOfLine('e')
	}
	else
	{
		while(lable[i]!='\0')
		{
			fputc(lable[i],fp);
			i++;
		}
		printAtEndOfLine('r')
	}
}

/* check if lable is extern */
static int isExtern(char *lable)
{
	if(list_find(ext_list, lable) != NULL)
		return true;
	return false;
}

/* add the operand info to tha array */
static int addOprand(char *array,int k,int opType ,char *operand)
{
	int j=0;
	int num;
	char *charP = toCharArray(opType);
	
	while(j<3) /* add the info about the type */
	{
		array[k+j] = charP[j+outLineLength-3];
		j++;
	}
	free(charP);
	k += j;
	
	j=0;
	/* add info about the rgister (if has) */
	if(isRegisterType(opType))
	{
		if(opType == registerIndex) /* finding the register */
		{
			while(*operand != '[')
			{
				operand++;
			}
			operand++;
		}
		
		operand++;
		num = *operand -'0';
		charP = toCharArray(num);
		
		while(j<3)
		{
			array[k+j] = charP[j+outLineLength-3];
			j++;
		}
		free(charP);
		k += j;
	}
	else
	{
		while(j<3)
		{
			array[k+j] = '0';
			j++;
		}
		k += j;
	}
	
	return k;
}

/* check the operand type
 * op: the string that need to be checked
 * return the operand type */
static int opType(char *op)
{
	int i;
	if(op[0] == NUM_START) /* check if the operand is a number */
	{
		i=1;
		while(op[i] != '\0')
		{
			if(!isdigit(op[i]))
			{
				if(!(i == 1 && (op[i] == '+' || op[i] == '-')))
				{
					return ERROR;
				}
			}
			i++;
		}
		return immediate;
	}
	if(op[0] == 'r') /* check if the operand is register */
	{
		if(isdigit(op[1]) && (op[1]-'0') < numOfRegisters && op[2] == '\0')
		{
			return registerImmediate;
		}
	}
	
	i = 0;
	while(op[i] != '\0')/* check if index type */
	{
		if(op[i] == '[')
		{
			if(op[i+1] == 'r' && isdigit(op[i+2]) && (op[i+2]-'0') < numOfRegisters && op[i+3] == ']' && op[i+4] == '\0') /* check if register index type */
			{
				return registerIndex;
			}
			/* check if changeable indext type or an error */
			i++;
			while(op[i] != ']')
			{
				if (!isalnum(op[i]))
				{
					return ERROR;
				}
				i++;
			}
			i++;
			while(op[i] != '\0')
			{
				if (!isalnum(op[i]))
				{
					return ERROR;
				}
				i++;
			}
			return changeableIndex;
		}
		else if (!isalnum(op[i]))
		{
			return ERROR;
		}
		i++;
	}
	return direct;
}

/* check the line type
 * st: the line
 * return the line type */
static int lineType(char *st)
{
	char str[LABLE_MAX_LENGTH+2]; /* +1 for :, +1 for \0 */
	if(0 == sscanf(st,"%s",str))
	{
		return empty;
	}
	if(str[0] == ';')
	{
		return comment;
	}
	if(str[0] == '.')
	{
		if(strcmp(str,".data") == 0)
		{
			return dat;
		}
		if(strcmp(str,".string") == 0)
		{
			return dot_str;
		}
		if(strcmp(str,".entry") == 0)
		{
			return ent;
		}
		if(strcmp(str,".extern") == 0)
		{
			return ext;
		}
	}
	else
	{
		int i = 0;
		while(i < LABLE_MAX_LENGTH+1)
		{
			if('\0' == str[i])
			{
				return regular;
			}
			if(':' == str[i])
			{
				return lable;
			}
			i++;
		}
		return regular;
	}
	
	return ERROR;
}

/* check if the string type line is legal */
static int checkStringLegalty(char *line, int lineNumber)
{
	/* skips ".string" */
	while(isspace(*line))
		line++;
	while(!isspace(*line))
		line++;
	
	while(*line != '\0' && isspace(*line))
	{
		line++;
	}
	
	if(*line != '\"')
	{
		do_error("unlegal syntax",lineNumber);
		return false;
	}
	line++;
	
	while(*line != '\0' && *line != '\"')
	{
		line++;
	}
	
	if(*line != '\"')
	{
		do_error("unlegal syntax",lineNumber);
		return false;
	}
	line++;
	
	while(*line != '\0')
	{
		if(!isspace(*line))
		{
			do_error("unlegal syntax",lineNumber);
			return false;
		}
		line++;
	}
	return true;
}

static void stringDirective(char *line, int lineNumber)
{
	int length;
	char* p_derective;
	char* p_str_start;
	char* p_str_end;
	int i;
	char asci_val;

	while(isspace(*line))
		line++;
	
	if(checkStringLegalty(line,lineNumber) == false)
	{
		return;
	}

	/* split the derective to the 3 parts */
	p_derective = strtok (line, "\"");
	p_str_start = strtok (NULL, "\"");
	p_str_end = strtok (NULL, "\"");

	/*printf("p_derective:%s\np_str_start:%s\np_str_end:%s\n", p_derective, p_str_start, p_str_end);*/

	length = strlen(p_str_start);

	for(i=0; i<length; i++)
	{
		asci_val = *(p_str_start + i);
		data_str_list = list_add(data_str_list, "", (int)asci_val);
		DC++;
	}

	data_str_list = list_add(data_str_list, "", (int)'\0');
	DC++;
}

static void dataDirective(char *line, int lineNumber)
{
	char* p_args;
	char* pch;
	int num;

	while(isspace(*line))
		line++;

	if(checkIf2Coma(line) == true)
	{
		do_error("2 comas near one other", lineNumber);
	}
	p_args = strstr(line, ".data") + strlen(".data");

	pch = strtok (p_args, ",");

	while (pch != NULL)
	{
		if(is_single_number(pch) == true)
		{
			num = atoi(pch);
			if(num<0) /* if negetiv use the compliting to, possible becouse int is more than 16 bits */
			{
				int i = 1;
				int mask = 1;
				while(i<16)
				{
					mask = mask*2 + 1;
					i++;
				}
				num = mask + num + 1;
			}
			data_str_list = list_add(data_str_list, "", num);

			pch = strtok (NULL, ",");

			DC++;
		}
		else
		{
			do_error("found illegal number", lineNumber);
			break;
		}
	}
}

static void printDataArray(FILE *fp)
{
	char* pch;
	node_ptr pnode;
	
	pnode = data_str_list;
	while(pnode != NULL)
	{
		pch = toCharArray(pnode->value);
		printLineAt4Base(pch, fp);
		printAtEndOfLine(' ')
		free(pch);
		pnode = pnode->next;
	}
}

static void updateLablesList(int update_val)
{
	node_ptr p;

	/* update address value */
	p = lable_list_tmp;
	while(p != NULL)
	{
		p->value += update_val;
		p = p->next;
	}
	
	/* add temp lables list to the main one */
	p = lable_list;
	
	if(lable_list == NULL)
	{
		lable_list = lable_list_tmp;
		return;
	}
	
	while(p->next != NULL)
	{
		p = p->next;
	}
	p->next = lable_list_tmp;
	
}

/* check if the is at line 2 coma neer one other */
static int checkIf2Coma(char *line)
{
	while(*line != '\0')
	{
		if(*line == ',' && line[1] == ',')
			return true;
		line++;
	}
	return false;
}
